﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Apworks.Repositories;
using TinyForum.Infrastructure;
using TinyForum.Domain.Model;
using TinyForum.DataObjects;
using Apworks.Specifications;
using Apworks.Storage;
using Microsoft.Practices.EnterpriseLibrary.Caching;

namespace TinyForum.Application.Implementation
{
    public class TopicApplication : ApplicationServiceBase, ITopicApplication
    {
        #region Internal Types
        internal class PostWithLatestReplyDate
        {
            internal Post Post { get; set; }
            internal Account PostUser { get; set; }
            internal Reply LatestReply { get; set; }
            internal Account LatestReplyUser { get; set; }
            internal int? TotalReplies { get; set; }

            internal PostWithLatestReplyDate() { }
            internal PostWithLatestReplyDate(Post post, Reply latestReply, Account postUser, Account latestReplyUser, int? totalReplies)
            {
                this.Post = post;
                this.PostUser = postUser;
                this.LatestReply = latestReply;
                this.LatestReplyUser = latestReplyUser;
                this.TotalReplies = totalReplies;
            }
        }
        #endregion

        #region Private Fields
        private readonly ICacheManager cacheManager = CacheFactory.GetCacheManager("DefaultCacheManager");
        private readonly string topicDetailsCacheKey = typeof(IEnumerable<TopicDetailDataObject>).FullName;
        #endregion

        #region Private Methods
        private void ClearTopicDetailsCache()
        {
            if (cacheManager.Contains(topicDetailsCacheKey))
                cacheManager.Remove(topicDetailsCacheKey);
        }

        private TopicDetailDataObject BuildTopicDetailDataObject(Topic topic, IRepository<Post> postRepository, IRepository<Reply> replyRepository, IRepository<Account> userRepository)
        {
            long topicId = topic.ID;
            IEnumerable<Post> posts = postRepository.FindAll(Specification<Post>.Eval(p => p.TopicID == topicId), 
                sortPred => sortPred.DateCreated, 
                SortOrder.Descending);
            Post latestPost = null;
            Reply latestReply = null;
            Account latestPostUser = null;
            Account latestReplyUser = null;
            int numOfPosts = 0;
            if (posts != null && posts.Count() > 0)
            {
                numOfPosts = posts.Count();
                latestPost = posts.FirstOrDefault();
                latestPostUser = userRepository.Get(Specification<Account>.Eval(u => u.ID == latestPost.UserID));
                List<Reply> latestRepliesDict = new List<Reply>();
                foreach (var post in posts)
                {
                    IEnumerable<Reply> replies = replyRepository.FindAll(Specification<Reply>.Eval(r => r.PostID == post.ID), 
                        sortPredicate => sortPredicate.DateCreated, 
                        SortOrder.Descending);
                    Reply latestReplyInPost = replies.FirstOrDefault();
                    if (latestReplyInPost!=null)
                        latestRepliesDict.Add(latestReplyInPost);
                }
                if (latestRepliesDict.Count > 0)
                {
                    latestRepliesDict.Sort((r1, r2) =>
                        {
                            if (r1.DateCreated > r2.DateCreated)
                                return -1;
                            else if (r1.DateCreated == r2.DateCreated)
                                return 0;
                            else return 1;
                        });
                    latestReply = latestRepliesDict[0];
                    latestReplyUser = userRepository.Get(Specification<Account>.Eval(u => u.ID == latestReply.UserID));
                }
            }
            
            return DataObjectMapper.MapToDataObject(topic, latestPost, latestReply, latestPostUser, latestReplyUser, numOfPosts);

        }
        #endregion

        #region ITopicApplication Members

        public long CreateTopic(string name, string description)
        {
            using (RepositoryContextManager mgr = new RepositoryContextManager())
            {
                Topic topic = new Topic { Name = name, Description = description };
                IRepository<Topic> topicRepository = mgr.GetRepository<Topic>();
                topicRepository.Add(topic);
                mgr.Commit();
                ClearTopicDetailsCache();
                return topic.ID;
            }
        }

        public TopicDataObject GetTopic(long id)
        {
            using (RepositoryContextManager mgr = new RepositoryContextManager())
            {
                IRepository<Topic> topicRepository = mgr.GetRepository<Topic>();
                Topic topic = topicRepository.Get(Specification<Topic>.Eval(p => p.ID == id));
                return DataObjectMapper.MapToDataObject(topic);
            }
        }

        public TopicDetailDataObject GetTopicDetail(long topicId)
        {
            using (RepositoryContextManager mgr = new RepositoryContextManager())
            {
                IRepository<Topic> topicRepository = mgr.GetRepository<Topic>();
                IRepository<Post> postRepository = mgr.GetRepository<Post>();
                IRepository<Reply> replyRepository = mgr.GetRepository<Reply>();
                IRepository<Account> userRepository = mgr.GetRepository<Account>();
                Topic topic = topicRepository.Get(Specification<Topic>.Eval(p => p.ID == topicId));
                return BuildTopicDetailDataObject(topic, postRepository, replyRepository, userRepository);
            }
        }

        public IEnumerable<TopicDetailDataObject> GetTopicDetails()
        {
            if (cacheManager.Contains(topicDetailsCacheKey))
            {
                return cacheManager[topicDetailsCacheKey] as IEnumerable<TopicDetailDataObject>;
            }
            else
            {
                List<TopicDetailDataObject> result = new List<TopicDetailDataObject>();
                using (RepositoryContextManager mgr = new RepositoryContextManager())
                {
                    IRepository<Topic> topicRepository = mgr.GetRepository<Topic>();
                    IRepository<Post> postRepository = mgr.GetRepository<Post>();
                    IRepository<Reply> replyRepository = mgr.GetRepository<Reply>();
                    IRepository<Account> userRepository = mgr.GetRepository<Account>();
                    IEnumerable<Topic> allTopics = topicRepository.GetAll();
                    if (allTopics != null)
                    {
                        foreach (Topic topic in allTopics)
                        {
                            result.Add(BuildTopicDetailDataObject(topic, postRepository, replyRepository, userRepository));
                        }
                    }
                    cacheManager.Add(topicDetailsCacheKey, result);
                    return result;
                }
            }
        }

        public IEnumerable<PostDataObject> GetPosts(long topicId, PostSortDataObject postSort, SortOrderDataObject sortOrder)
        {
            List<PostDataObject> result = new List<PostDataObject>();
            using (RepositoryContextManager mgr = new RepositoryContextManager())
            {
                IRepository<Topic> topicRepository = mgr.GetRepository<Topic>();
                if (!topicRepository.Exists(Specification<Topic>.Eval(p => p.ID == topicId)))
                    throw new TinyForumApplicationException("The topic (ID={0}) does not exist.", topicId);
                IRepository<Post> postRepository = mgr.GetRepository<Post>();
                IRepository<Reply> replyRepository = mgr.GetRepository<Reply>();
                IRepository<Account> userRepository = mgr.GetRepository<Account>();
                IEnumerable<Post> allPosts = null;
                switch (postSort)
                {
                    case PostSortDataObject.PostDate:
                        SortOrder so = SortOrder.Unspecified;
                        switch (sortOrder)
                        {
                            case SortOrderDataObject.Ascending:
                                so = SortOrder.Ascending;
                                break;
                            default:
                                so = SortOrder.Descending;
                                break;
                        }
                        allPosts = postRepository.FindAll(Specification<Post>.Eval(p => p.TopicID == topicId),
                            sortPredicate => sortPredicate.DateCreated, so);
                        foreach (var post in allPosts)
                        {
                            Account postUser = userRepository.Get(Specification<Account>.Eval(u => u.ID == post.UserID));
                            IEnumerable<Reply> repliesForPost = replyRepository.FindAll(Specification<Reply>.Eval(r => r.PostID == post.ID),
                                sortPred => sortPred.DateCreated, SortOrder.Descending);

                            if (repliesForPost != null && repliesForPost.Count() > 0)
                            {
                                int totalReplies = repliesForPost.Count();
                                Reply latestReplyForPost = repliesForPost.First();
                                Account replyUser = userRepository.Get(Specification<Account>.Eval(u => u.ID == latestReplyForPost.UserID));
                                result.Add(DataObjectMapper.MapToDataObject(post, latestReplyForPost, postUser, replyUser, totalReplies));
                            }
                            else
                                result.Add(DataObjectMapper.MapToDataObject(post, postUser));
                        }
                        break;
                    case PostSortDataObject.LatestReplyDate:
                        allPosts = postRepository.FindAll(Specification<Post>.Eval(p => p.TopicID == topicId));
                        List<PostWithLatestReplyDate> tmpPosts = new List<PostWithLatestReplyDate>();
                        foreach (var post in allPosts)
                        {
                            long postId = post.ID;
                            Account postUser = userRepository.Get(Specification<Account>.Eval(u=>u.ID==post.UserID));
                            IEnumerable<Reply> allRepliesForPost = replyRepository.FindAll(Specification<Reply>.Eval(r => r.PostID == postId));
                            if (allRepliesForPost != null && allRepliesForPost.Count() > 0)
                            {
                                Reply latestReplyForPost = allRepliesForPost.OrderByDescending(r => r.DateCreated).First();
                                Account latestReplyUser = userRepository.Get(Specification<Account>.Eval(u=>u.ID==latestReplyForPost.UserID));
                                tmpPosts.Add(new PostWithLatestReplyDate(post, latestReplyForPost, postUser, latestReplyUser, allRepliesForPost.Count()));
                            }
                            else
                                tmpPosts.Add(new PostWithLatestReplyDate(post, null, postUser, null, null));
                        }
                        switch (sortOrder)
                        {
                            case SortOrderDataObject.Ascending:
                                tmpPosts.OrderBy(p => p.LatestReply.DateCreated).ToList().ForEach(q => 
                                    {
                                        result.Add(DataObjectMapper.MapToDataObject(q.Post, q.LatestReply, q.PostUser, q.LatestReplyUser, q.TotalReplies));
                                    });
                                break;
                            case SortOrderDataObject.Descending:
                                tmpPosts.OrderByDescending(p => p.LatestReply.DateCreated).ToList().ForEach(q => 
                                    {
                                        result.Add(DataObjectMapper.MapToDataObject(q.Post, q.LatestReply, q.PostUser, q.LatestReplyUser, q.TotalReplies));
                                    });
                                break;
                            default: break;
                        }
                        break;
                }
            }
            return result;
        }
        #endregion
    }
}
